package cn.lili.modules.search.serviceimpl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.lili.cache.Cache;
import cn.lili.cache.CachePrefix;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.vo.PageVO;
import cn.lili.modules.goods.entity.dos.Goods;
import cn.lili.modules.goods.service.GoodsService;
import cn.lili.modules.search.entity.dos.EsGoodsIndex;
import cn.lili.modules.search.entity.dos.EsGoodsRelatedInfo;
import cn.lili.modules.search.entity.dos.EsSupplierGoodsIndex;
import cn.lili.modules.search.entity.dto.EsGoodsSearchDTO;
import cn.lili.modules.search.service.EsGoodsSearchAbstractService;
import cn.lili.modules.search.service.EsGoodsSearchService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.aggregations.metrics.TopHitsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.collapse.CollapseBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.elasticsearch.core.*;
import org.springframework.data.elasticsearch.core.clients.elasticsearch7.ElasticsearchAggregations;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * ES商品搜索业务层实现
 *
 * @author paulG
 * @since 2020/10/16
 **/
@Slf4j
@Service
@RequiredArgsConstructor
public class EsGoodsSearchServiceImpl extends EsGoodsSearchAbstractService implements EsGoodsSearchService {

    /**
     * ES
     */
    private final ElasticsearchOperations restTemplate;
    /**
     * 缓存
     */
    private final Cache<Object> cache;

    private final GoodsService goodsService;

    @Override
    public SearchPage<EsGoodsIndex> searchGoods(EsGoodsSearchDTO searchDTO, PageVO pageVo) {
        return this.searchGoods(searchDTO, pageVo, EsGoodsIndex.class,true);
    }

    @Override
    public <T> SearchPage<T> searchGoods(EsGoodsSearchDTO searchDTO, PageVO pageVo, Class<T> clazz,Boolean flag) {
        if (CharSequenceUtil.isNotBlank(searchDTO.getKeyword())) {
            cache.incrementScore(CachePrefix.HOT_WORD.getPrefix(), searchDTO.getKeyword());
        }
        NativeSearchQueryBuilder searchQueryBuilder = createSearchQueryBuilder(searchDTO, pageVo);
        //2.2 指定高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("goodsName")
                .preTags("<font color='red'>")
                .postTags("</font>");
        searchQueryBuilder.withHighlightBuilder(highlightBuilder);
        if(flag){
            //指定去重字段
            searchQueryBuilder.withCollapseField("goodsId.keyword");
        }
        NativeSearchQuery searchQuery = searchQueryBuilder.build();

        SearchHits<T> search = restTemplate.search(searchQuery, clazz);
        SearchPage<T> searchHits = SearchHitSupport.searchPageFor(search, searchQuery.getPageable());
        return searchHits;
    }

    @Override
    public <T> Page<T> searchGoodsByPage(EsGoodsSearchDTO searchDTO, PageVO pageVo, Class<T> clazz,boolean flag) {
        SearchPage<T> esGoodsIndices = this.searchGoods(searchDTO, pageVo, clazz,flag);
        Page<T> resultPage = new Page<>();
        if (esGoodsIndices != null && !esGoodsIndices.getContent().isEmpty()) {
            List<T> collect = esGoodsIndices.getSearchHits().getSearchHits().stream().map(SearchHit::getContent).toList();
            resultPage.setRecords(collect);
            resultPage.setPages(esGoodsIndices.getTotalPages());
            resultPage.setCurrent(esGoodsIndices.getNumber() + 1L);
            resultPage.setSize(esGoodsIndices.getSize());
            resultPage.setTotal(esGoodsIndices.getTotalElements());
        }
        return resultPage;
    }


    @Override
    public EsGoodsRelatedInfo getSelector(EsGoodsSearchDTO goodsSearch, PageVO pageVo) {
        NativeSearchQueryBuilder builder = createSearchQueryBuilder(goodsSearch, pageVo);
        //分类
        AggregationBuilder categoryNameBuilder = AggregationBuilders.terms(ATTR_CATEGORY_NAME).field("categoryNamePath.keyword");
        builder.withAggregations(AggregationBuilders.terms("categoryAgg").field(ATTR_CATEGORY_PATH_NAME).subAggregation(categoryNameBuilder));

        //品牌
        AggregationBuilder brandNameBuilder = AggregationBuilders.terms(ATTR_BRAND_NAME).field("brandName.keyword");
        builder.withAggregations(AggregationBuilders.terms("brandIdNameAgg").field(ATTR_BRAND_ID).size(Integer.MAX_VALUE).subAggregation(brandNameBuilder));
        AggregationBuilder brandUrlBuilder = AggregationBuilders.terms(ATTR_BRAND_URL).field("brandUrl.keyword");
        builder.withAggregations(AggregationBuilders.terms("brandIdUrlAgg").field(ATTR_BRAND_ID).size(Integer.MAX_VALUE).subAggregation(brandUrlBuilder));
        //参数
        AggregationBuilder valuesBuilder = AggregationBuilders.terms("valueAgg").field(ATTR_VALUE);
        AggregationBuilder sortBuilder = AggregationBuilders.sum("sortAgg").field(ATTR_SORT);
        AggregationBuilder paramsNameBuilder = AggregationBuilders.terms("nameAgg").field(ATTR_NAME).subAggregation(sortBuilder).order(BucketOrder.aggregation("sortAgg", false)).subAggregation(valuesBuilder);
        builder.withAggregations(AggregationBuilders.nested("attrAgg", ATTR_PATH).subAggregation(paramsNameBuilder));
        NativeSearchQuery searchQuery = builder.build();
        try {
            SearchHits<EsGoodsIndex> search = restTemplate.search(searchQuery, EsGoodsIndex.class);
            log.debug("getSelector DSL:{}", searchQuery.getQuery());
            log.debug("getSelector DSL:{}", searchQuery.getAggregations());
            Map<String, Aggregation> aggregationMap = Objects.requireNonNull((ElasticsearchAggregations) search.getAggregations()).aggregations().getAsMap();
            return convertToEsGoodsRelatedInfo(aggregationMap, goodsSearch);
        }catch (Exception e){
            log.error(e.getMessage());
        }
        return null;
    }

    @Override
    public List<EsGoodsIndex> getEsGoodsBySkuIds(List<String> skuIds) {
        NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder();
        NativeSearchQuery build = searchQueryBuilder.build();
        build.setIds(skuIds);
        List<MultiGetItem<EsGoodsIndex>> multiGetItems = restTemplate.multiGet(build, EsGoodsIndex.class, restTemplate.getIndexCoordinatesFor(EsGoodsIndex.class));
        return multiGetItems.stream().map(MultiGetItem::getItem).collect(Collectors.toList());
    }

    /**
     * 根据id获取商品索引
     *
     * @param id 商品skuId
     * @return 商品索引
     */
    @Override
    public EsGoodsIndex getEsGoodsById(String id) {
        return this.restTemplate.get(id, EsGoodsIndex.class);
    }

    @Override
    public void searchConversion(List<EsSupplierGoodsIndex> supplierGoodsIndices) {
        if (CollectionUtil.isEmpty(supplierGoodsIndices)) {
            return;
        }
        List<String> goodsIdList = supplierGoodsIndices.stream().map(EsSupplierGoodsIndex::getGoodsId).toList();
        if (CollectionUtil.isEmpty(goodsIdList)) {
            return;
        }
        //这些商品在当前店铺已存在则说明以代理
        LambdaQueryWrapper<Goods> goodsLambdaQueryWrapper = new LambdaQueryWrapper<>();
        goodsLambdaQueryWrapper.in(Goods::getSupplierGoodsId, goodsIdList);
        goodsLambdaQueryWrapper.in(Goods::getStoreId, UserContext.getCurrentUser().getExtendId());
        goodsLambdaQueryWrapper.eq(Goods::getDeleteFlag, Boolean.FALSE);
        goodsLambdaQueryWrapper.select(Goods::getId, Goods::getSupplierGoodsId,Goods::getStoreId);
        List<Goods> goodsList = goodsService.list(goodsLambdaQueryWrapper);

        supplierGoodsIndices.forEach(f -> f.setIsProxy(false));
        if (CollectionUtil.isEmpty(goodsList)) {
            return;
        }
        for (Goods goods : goodsList) {
            for (EsSupplierGoodsIndex supplierGoodsIndex : supplierGoodsIndices) {
                if (goods.getSupplierGoodsId().equals(supplierGoodsIndex.getGoodsId())) {
                    supplierGoodsIndex.setIsProxy(true);
                }
            }
        }
    }
}
